Системное программирование

Тема 1. Прикладной программный интерфейс

Системное программирование

План лекции

1. Современные инструментальные средства разработки системно-ориентированных приложений

2. Основы организации нативного прикладного программного интерфейса операционной системы (API)

3. WinAPI — программный интерфейс Windows

4. Системные вызовы Linux

5. Прерывания и их обработка

Прикладной программный интерфейс
Системное программирование

Введение в дисциплину

Что такое системное программирование?

Системное программирование — разработка программного обеспечения, непосредственно взаимодействующего с операционной системой и аппаратным обеспечением компьютера.

Системное программирование

  • Работа с процессами и потоками
  • Управление памятью
  • Межпроцессное взаимодействие
  • Системные вызовы и API
  • Драйверы и модули ядра

Прикладное программирование

  • Веб-приложения и API
  • Базы данных
  • Пользовательский интерфейс
  • Бизнес-логика
Прикладной программный интерфейс
Системное программирование

Зачем изучать системное программирование?

  • Понимание ОС — фундамент для любого разработчика
  • Производительность — написание эффективного кода
  • Отладка — диагностика проблем на низком уровне
  • Безопасность — понимание уязвимостей и их предотвращение
  • Карьера — системный разработчик, DevOps, embedded, безопасность

Структура курса

Тема Содержание
1. Прикладной программный интерфейс API, системные вызовы, прерывания
2. Процессы и задания Управление процессами и потоками
3. Объекты ядра Синхронизация, мьютексы, семафоры
4. Параллельная обработка Многопоточность, состязания
5. Механизм сообщений Оконный интерфейс, события
6. Графический интерфейс GUI, контекст устройства
7. Память DLL, проецирование файлов
Прикладной программный интерфейс
Системное программирование

1. Современные инструментальные средства разработки

1.1. Инструменты разработки системного ПО

Компиляторы и среды разработки:

Среда Платформа Особенности
Microsoft Visual Studio Windows Интеграция с WinAPI, отладчик, профилировщик
VS Code + extensions Кроссплатформенная Лёгкая, расширяемая, C/C++ extension
CLion Кроссплатформенная JetBrains, CMake, рефакторинг
Qt Creator Кроссплатформенная Интеграция с Qt, CMake, qmake
GCC (GNU Compiler Collection) Кроссплатформенная Стандарт Linux, оптимизации
Clang/LLVM Кроссплатформенная Современный, модульный, статический анализ
MinGW-w64 Windows Порт GCC для Windows, 32/64-bit
Прикладной программный интерфейс
Системное программирование

Системы сборки:

  • CMake — кроссплатформенная система сборки
  • Meson — быстрая современная система
  • Ninja — быстрый бэкенд для сборки
  • Make — классический инструмент
Прикладной программный интерфейс
Системное программирование

Отладчики и профилировщики:

Инструмент Платформа Назначение
GDB Linux/macOS Отладка программ
LLDB macOS/Linux Отладчик LLVM
WinDbg Windows kernel/user-mode отладка
Valgrind Linux Анализ памяти, кэша
AddressSanitizer Кроссплатформенный Обнаружение ошибок памяти
strace/ltrace Linux Трассировка системных вызовов
perf Linux Профилирование производительности
Прикладной программный интерфейс
Системное программирование

1.1.1. Rust — язык системного программирования

Rust — современный язык системного программирования, разработанный Mozilla Research.

Ключевые особенности:

  • Безопасность памяти без GC — система владения (ownership) и заимствования (borrowing)
  • Zero-cost abstractions — абстракции не требуют накладных расходов во время выполнения
  • Fearless concurrency — безопасная многопоточность на уровне компилятора
  • Современная система сборки — Cargo (сборка, тестирование, зависимости)
Прикладной программный интерфейс
Системное программирование

Применение в системном программировании:

  • Разработка компонентов ядра Linux (поддержка Rust добавлена в ядро 6.1)
  • Embedded-системы и микроконтроллеры (no_std)
  • Драйверы устройств
  • Системные утилиты и CLI-инструменты

Пример функции на Rust:

fn read_file_contents(path: &str) -> Result<String, std::io::Error> {
    std::fs::read_to_string(path)
}

fn main() {
    match read_file_contents("/etc/hostname") {
        Ok(contents) => println!("{}", contents.trim()),
        Err(e) => eprintln!("Ошибка: {}", e),
    }
}
Прикладной программный интерфейс
Системное программирование

1.1.2. Отладка и профилирование

GDB (GNU Debugger)

Основные команды GDB:

Команда Сокращение Назначение
break main b main Установить точку останова
run r Запустить программу
step s Шаг с заходом в функции
next n Шаг без захода в функции
print var p var Вывести значение переменной
backtrace bt Вывести стек вызовов
watch var Следить за изменением переменной
continue c Продолжить выполнение
quit q Выйти из GDB
Прикладной программный интерфейс
Системное программирование

Пример сессии отладки:

$ gcc -g -o program program.c
$ gdb ./program
(gdb) break main
(gdb) run arg1 arg2
(gdb) next
(gdb) print buffer
(gdb) backtrace
(gdb) continue
Прикладной программный интерфейс
Системное программирование

Valgrind

Valgrind — набор инструментов для динамического анализа программ.

Инструмент Назначение
Memcheck Обнаружение ошибок памяти (утечки, выход за границы)
Cachegrind Профилирование кэша процессора
Callgrind Построение графа вызовов и подсчёт инструкций
Helgrind Обнаружение ошибок синхронизации в потоках

Пример использования Memcheck:

$ gcc -g -o program program.c
$ valgrind --leak-check=full --show-leak-kinds=all ./program
Прикладной программный интерфейс
Системное программирование

AddressSanitizer (ASan)

AddressSanitizer — встроенный в GCC/Clang инструмент обнаружения ошибок памяти.

Обнаруживаемые ошибки:

  • Выход за границы буфера (buffer overflow)
  • Использование освобождённой памяти (use-after-free)
  • Двойное освобождение (double free)
  • Утечки памяти (memory leak)
  • Использование неинициализированной памяти (MSan)
Прикладной программный интерфейс
Системное программирование

Использование:

$ gcc -fsanitize=address -g -o program program.c
$ ./program

Преимущества перед Valgrind:

  • Замедление программы в 2-3 раза (vs 10-50x у Valgrind)
  • Встроен в компилятор — не требует отдельной установки
  • Точный отчёт с указанием строки кода
Прикладной программный интерфейс
Системное программирование

strace и ltrace

strace — трассировка системных вызовов и сигналов.

$ strace ./program
$ strace -e open,read,write ./program
$ strace -p 1234

ltrace — трассировка вызовов библиотечных функций.

$ ltrace ./program
$ ltrace -e malloc,free ./program

Практическое применение:

  • Диагностика ошибок ввода-вывода (какие файлы открывает программа)
  • Анализ производительности (подсчёт системных вызовов)
  • Изучение поведения незнакомых программ
Прикладной программный интерфейс
Системное программирование

WinDbg

WinDbg — отладчик для Windows, поддерживающий отладку как пользовательского, так и kernel-режима.

Ключевые возможности:

  • Отладка kernel-mode драйверов и BSOD (Blue Screen of Death)
  • Анализ дампов памяти (crash dumps)
  • Расширения !analyze, !process, !thread
  • Доступен через Microsoft Store и Windows SDK
Прикладной программный интерфейс
Системное программирование

1.2. Кроссплатформенные библиотеки

Библиотеки абстракции платформы:

Библиотека Назначение Особенности
SDL Мультимедиа, игры Аудио, видео, ввод, таймеры
Qt GUI, системные функции Сигналы/слоты, MOC, богатый набор виджетов
GTK GUI Linux-ориентированная, GObject
wxWidgets GUI Нативные виджеты ОС
Boost Утилиты, многопоточность ASIO, filesystem, thread
POCO Сеть, утилиты HTTP, JSON, XML
libuv Асинхронный I/O Node.js runtime
Прикладной программный интерфейс
Системное программирование

Пример кроссплатформенного кода:

// Кроссплатформенное создание потока с std::thread (C++11)
#include <thread>
#include <iostream>

void worker(int id) {
    std::cout << "Thread " << id << " running" << std::endl;
}

int main() {
    std::thread t1(worker, 1);
    std::thread t2(worker, 2);
    t1.join();
    t2.join();
    return 0;
}

Абстракция файловой системы (C++17):

#include <filesystem>
namespace fs = std::filesystem;

// Кроссплатформенная работа с файлами
fs::path p = "/home/user/document.txt";
bool exists = fs::exists(p);
fs::file_size(p);
fs::create_directory(fs::path("/tmp/mydir"));
Прикладной программный интерфейс
Системное программирование

1.3. Специфика системного программирования

Особенности системного ПО

  • Прямое взаимодействие с ОС и оборудованием
  • Высокие требования к производительности
  • Критичность к ошибкам и сбоям
  • Платформенная зависимость
  • Низкоуровневый доступ к ресурсам

Требования к разработчику

  • Глубокое понимание архитектуры ОС
  • Знание ассемблера и системных вызовов
  • Умение работать с памятью напрямую
  • Понимание механизмов синхронизации
  • Навыки отладки на низком уровне
Прикладной программный интерфейс
Системное программирование

2. Основы организации API операционной системы

2.1. Что такое API?

API (Application Programming Interface) — набор готовых классов, функций, структур и констант, предоставляемых операционной системой для создания приложений.

Уровни программных интерфейсов:

Уровень Примеры Назначение
Прикладной WinAPI, POSIX Разработка приложений
Системный вызов int 0x80, syscall Взаимодействие с ядром
Аппаратный BIOS, UEFI Низкоуровневый доступ
Прикладной программный интерфейс
Системное программирование

2.2. Архитектура API

Компоненты прикладного интерфейса:

Прикладной программный интерфейс
Системное программирование

2.3. Стандарт POSIX

POSIX (Portable Operating System Interface) — семейство стандартов IEEE, определяющих интерфейс между операционной системой и прикладными программами.

Ключевые характеристики:

  • Определяет набор системных вызовов и библиотечных функций (libc)
  • Обеспечивает переносимость программ между UNIX-подобными ОС
  • Реализован в Linux, macOS, FreeBSD, QNX и др.
  • Текущая версия — POSIX.1-2017 (IEEE Std 1003.1)
Компонент POSIX Описание
pthread Потоки и синхронизация
fork/exec Управление процессами
open/read/write Работа с файлами
socket Сетевое взаимодействие
signal/sigaction Обработка сигналов
sem/mutex Синхронизация (семафоры, мьютексы)
mmap Отображение файлов в память
Прикладной программный интерфейс
Системное программирование

3. WinAPI — программный интерфейс Windows

3.1. Основные характеристики WinAPI

WinAPI (Windows API) — основной программный интерфейс операционных систем семейства Windows.

Ключевые особенности:

  • Реализован в виде DLL-библиотек (kernel32.dll, user32.dll, gdi32.dll)
  • Использует соглашение вызовов stdcall
  • Поддерживает Unicode (UTF-16) и ANSI кодировки
  • Обширная документация от Microsoft

Основные DLL:

  • kernel32.dll — управление памятью, процессами, потоками
  • user32.dll — оконный интерфейс, ввод с клавиатуры и мыши
  • gdi32.dll — графический вывод, рисование
Прикладной программный интерфейс
Системное программирование

3.2. Структура WinAPI-программы

Минимальная программа на WinAPI:

#include <windows.h>

int WINAPI WinMain(HINSTANCE hInstance, 
                   HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine, 
                   int nCmdShow)
{
    MessageBox(NULL, 
               "Hello, World!", 
               "My First WinAPI App",
               MB_OK);
    return 0;
}

Типы данных WinAPI:

  • HANDLE — дескриптор объекта
  • HWND — дескриптор окна
  • HINSTANCE — дескриптор экземпляра приложения
  • LPSTR/LPWSTR — указатели на строки
Прикладной программный интерфейс
Системное программирование

3.3. Системные вызовы в Windows

Механизм системных вызовов:

  1. Приложение вызывает функцию из ntdll.dll
  2. Происходит переход в режим ядра (sysenter/syscall)
  3. Выполняется код ядра (ntoskrnl.exe)
  4. Возврат в пользовательский режим

Пример системного вызова:

// Высокоуровневая функция
HANDLE CreateFile(...);

// Низкоуровневый системный вызов
NTSTATUS NtCreateFile(...);
Прикладной программный интерфейс
Системное программирование

4. Системные вызовы Linux

4.1. Интерфейс системных вызовов Linux

Системные вызовы Linux — механизм взаимодействия пользовательских программ с ядром.

Способы вызова:

  • Через библиотеку libc (рекомендуется)
  • Непосредственно через прерывание/инструкцию syscall
  • Через библиотеку syscall()

Традиционный механизм (x86):

mov eax, 4          ; номер системного вызова (write)
mov ebx, 1          ; файловый дескриптор (stdout)
mov ecx, message    ; указатель на сообщение
mov edx, length     ; длина сообщения
int 0x80            ; прерывание ядра
Прикладной программный интерфейс
Системное программирование

4.2. Основные системные вызовы Linux

Категории системных вызовов:

Категория Примеры Описание
Файлы open, read, write, close Работа с файлами
Процессы fork, exec, wait, exit Управление процессами
Память mmap, brk, mprotect Управление памятью
Сигналы kill, signal, sigaction Обработка сигналов
IPC pipe, shmget, semop Межпроцессное взаимодействие

Пример на C:

#include <unistd.h>
#include <fcntl.h>

int fd = open("file.txt", O_RDONLY);
char buffer[1024];
ssize_t n = read(fd, buffer, sizeof(buffer));
close(fd);
Прикладной программный интерфейс
Системное программирование

4.3. Сравнение WinAPI и Linux API

Windows (WinAPI)

  • Объектно-ориентированный подход
  • Дескрипторы HANDLE для всех ресурсов
  • Единообразная обработка ошибок (GetLastError)
  • Обширный набор функций GUI
  • Закрытый исходный код
  • Широкая документация MSDN

Linux (POSIX)

  • Файл-ориентированный подход ("всё есть файл")
  • Файловые дескрипторы (int)
  • Ошибки через errno
  • GUI через X11/Wayland/Qt/GTK
  • Открытые стандарты POSIX
  • man pages, исходный код ядра
Прикладной программный интерфейс
Системное программирование

Соответствие основных функций:

Операция Windows (WinAPI) Linux (POSIX)
Открыть файл CreateFile open
Читать ReadFile read
Писать WriteFile write
Закрыть CloseHandle close
Создать процесс CreateProcess fork + exec
Создать поток CreateThread pthread_create
Ожидание WaitForSingleObject pthread_join / wait
Мьютекс CreateMutex pthread_mutex_init
Сон Sleep usleep / nanosleep
Прикладной программный интерфейс
Системное программирование

5. Прерывания и их обработка

5.1. Концепция прерываний

Прерывание — сигнал, сообщающий процессору о наступлении какого-либо события, требующего немедленной реакции.

Типы прерываний:

  • Аппаратные — от устройств (клавиатура, таймер, диск)
  • Программные — инициированные программой (int 0x80, syscall)
  • Исключения — ошибки выполнения (деление на ноль, page fault)
Прикладной программный интерфейс
Системное программирование

Вектор прерываний:

Прикладной программный интерфейс
Системное программирование

5.2. Обработка прерываний

Механизм обработки:

  1. Возникновение прерывания
  2. Сохранение контекста (регистры, флаги)
  3. Определение обработчика по вектору
  4. Выполнение кода обработчика
  5. Восстановление контекста
  6. Возврат к прерванному коду (iret)

Современные механизмы:

  • MSR (Model Specific Registers) — быстрые системные вызовы
  • sysenter/sysexit — Intel
  • syscall/sysret — AMD/Intel x86-64
Прикладной программный интерфейс
Системное программирование

5.3. Практическое применение

Использование прерываний в системном программировании:

  • Реализация многозадачности (таймер)
  • Обработка ввода/вывода (асинхронность)
  • Взаимодействие с драйверами устройств
  • Реализация системных вызовов

Пример: обработка сигнала в Linux

#include <signal.h>

void handler(int sig) {
    printf("Получен сигнал %d\n", sig);
}

signal(SIGINT, handler);
Прикладной программный интерфейс
Системное программирование

Резюме

Ключевые моменты лекции:

  1. API — интерфейс между приложением и ОС
  2. WinAPI — мощный, но специфичный для Windows
  3. POSIX/Linux API — стандартный, переносимый
  4. Системные вызовы — основной механизм взаимодействия с ядром
  5. Прерывания — фундаментальный механизм управления в ОС

Что изучать дальше:

  • Процессы и потоки (Тема 2)
  • Объекты ядра и синхронизация (Тема 3)
  • Параллельная обработка (Тема 4)
Прикладной программный интерфейс
Системное программирование

Вопросы для самопроверки

  1. Что такое API и какие уровни он включает?
  2. Какие основные DLL составляют WinAPI?
  3. Как происходит системный вызов в Linux?
  4. Чем отличаются аппаратные и программные прерывания?
  5. Какие механизмы быстрых системных вызовов используются в современных процессорах?
Прикладной программный интерфейс
Системное программирование

Практические задания

Самостоятельная работа (6 часов):

  1. Настроить среду разработки для системного программирования
  2. Изучить структуру WinAPI-приложения и POSIX-программы
  3. Написать программу, использующую системные вызовы для работы с файлами
  4. Исследовать различия в обработке ошибок в Windows и Linux
Прикладной программный интерфейс
Системное программирование

Рекомендуемая литература

Основная:

  1. Таненбаум, Э. Современные операционные системы. — 4-е изд. — СПб.: Питер, 2021.
  2. Лав, Р. Linux. Системное программирование. — 2-е изд. — СПб.: Питер, 2024.
  3. Kerrisk, M. The Linux Programming Interface. — No Starch Press, 2010.

Дополнительная:

  1. Документация Microsoft Learn: https://learn.microsoft.com/en-us/windows/win32/
  2. Linux man pages: https://man7.org/linux/man-pages/
  3. Arch Linux man pages: https://man.archlinux.org/
  4. C++ Reference: https://en.cppreference.com/
  5. POSIX Standard: https://pubs.opengroup.org/onlinepubs/9699919799/
Прикладной программный интерфейс

Быстро пробежаться по плану (~1 мин), не зачитывать. Упомянуть, что начнём с инструментов, а основное внимание уделим API и системным вызовам.

Подчеркнуть ключевое отличие: системное ПО работает ближе к «железу» и ОС. Привести примеры: ОС, драйверы, антивирусы, компиляторы, утилиты (git, docker). Спросить, с каким типом программирования студенты уже знакомы.

Упомянуть, что курс охватывает обе основные платформы: Windows (WinAPI) и Linux (POSIX). Лабораторные работы — практическая основа курса.

Не dwell на каждом инструменте — опросить аудиторию, чем пользуются. Подчеркнуть, что GCC и Clang — ключевые для курса. Упомянуть, что Visual Studio удобна для WinAPI, но в Linux не работает.

CMake — основной инструмент для лабораторных. Make упомянуть как legacy, но широко используемый. Спросить, кто уже работал с CMake.

Выделить strace и AddressSanitizer — они будут активно использоваться в курсе. Спросить, кто знаком с GDB. Упомянуть, что AddressSanitizer встроен в GCC/Clang и не требует отдельной установки.

Rust — не замена C/C++, а альтернатива с акцентом на безопасность. Ядро Linux принимает Rust-модули с версии 6.1 (2022). Спросить студентов, кто слышал о Rust. Cargo — аналог CMake + пакетного менеджера в одном инструменте.

GDB — стандартный отладчик в Linux. Флаг -g добавляет отладочную информацию. Показать, что backtrace — первая команда при падении (segfault).

Valgrind замедляет программу в 10-50 раз — использовать на этапе тестирования, не в production. Memcheck — инструмент по умолчанию.

ASan — основной инструмент для обнаружения ошибок памяти в курсе. Флаг -g обязателен для читаемых отчётов. Упомянуть, что ASan и Valgrind дополняют друг друга.

strace — один из самых полезных инструментов системного программиста. Флаг -e фильтрует вызовы. Флаг -p подключается к запущенному процессу.

WinDbg — основной инструмент для отладки драйверов Windows. Упомянуть WinDbg Preview из Microsoft Store — современный интерфейс.

Подчеркнуть, что кроссплатформенность — не панацея: нужно тестировать на каждой платформе. Qt и Boost — наиболее востребованные в индустрии.

Показать, как std::filesystem абстрагирует различия ОС. Обратить внимание на fs::path — он корректно работает с разделителями на разных платформах. Можно предложить студентам скомпилировать и запустить пример.

Ключевой слайд — подчеркнуть разницу между системным и прикладным программированием. Ошибка в системном ПО может крашнуть всю систему (kernel panic, BSOD). Спросить студентов: какие последствия ошибки в веб-приложении vs драйвере?

Важно: многие студенты знают API из веб-контекста (REST API) — здесь API ОС принципиально другой. Подчеркнуть три уровня и переход между ними. Спросить: может ли приложение напрямую обратиться к оборудованию?

Важная диаграмма — пройти по каждому слою сверху вниз. Подчеркнуть границу user mode / kernel mode — приложение не может напрямую выполнять код ядра. libc выступает обёрткой над системными вызовами.

Подчеркнуть: POSIX — это *стандарт*, а Linux — его *реализация* (с расширениями). Программа, написанная на POSIX API, скомпилируется и будет работать на любой POSIX-совместимой ОС. WinAPI *не* является POSIX-совместимым (частично через Cygwin/WSL).

Упомянуть, что WinAPI содержит десятки тысяч функций — невозможно выучить все. Три основные DLL нужно знать наизусть. stdcall — callee очищает стек, в отличие от cdecl. Unicode-версии функций оканчиваются на W (Wide), ANSI — на A.

Показать, что WinMain заменяет main в GUI-программах Windows. HANDLE — это void*, но не стоит разыменовывать напрямую. Венгерская нотация: LP = Long Pointer, H = Handle, W = Wide. MessageBox — простейшая функция для демонстрации.

Подчеркнуть разницу между высокоуровневым (kernel32.dll → ntdll.dll → ядро) и прямым (ntdll.dll → ядро). Прямые вызовы Nt* функций не рекомендуются — они не документированы и могут измениться.

Важно: int 0x80 — устаревший механизм для x86. На x86-64 используется инструкция syscall с номерами через регистр rax. Номера syscalls различаются между архитектурами (x86 vs x86-64 vs ARM). Всегда рекомендовать libc.

Ключевая идея Linux: «всё есть файл» — сокеты, каналы, устройства доступны через те же open/read/write. Обратить внимание на проверку возвращаемых значений (ошибки!). Пример намеренно упрощён — в реальном коде нужна проверка fd >= 0 и обработка ошибок.

Фундаментальное различие философий: WinAPI — ООП-подход с дескрипторами-объектами, Linux — минимализм и «всё есть файл». Спросить студентов: какой подход кажется более последовательным? Упомянуть, что POSIX — стандарт, а Linux — его реализация (с расширениями).

Рекомендовать сохранить эту таблицу как шпаргалку. Обратить внимание на fork+exec vs CreateProcess — в Linux создание процесса разделено на два шага (копирование + замена образа), что даёт большую гибкость. Sleep(ms) vs usleep(мкс) — разница в единицах.

Привести аналогию: прерывание — как звонок телефона во время работы. Аппаратные — кто-то звонит, программные — вы сами набираете номер, исключения — авария. Подчеркнуть, что прерывания — основа многозадачности (timer interrupt).

Уточнить: 0x80 — для 32-битного Linux через int 0x80, на x86-64 системные вызовы идут через syscall и не используют этот вектор. Исключения CPU: 0 — division by zero, 13 — GPF, 14 — page fault. IRQ диапазон может быть перемещён (IRQ remapping в APIC).

Пройти все 6 шагов последовательно. Ключевой момент: шаг 2 (сохранение контекста) — без него прерванный код не сможет продолжить. sysenter/syscall быстрее int 0x80, т.к. не используют IDT и не проверяют сегментные регистры.

Важно: в обработчике сигнала можно вызывать только async-signal-safe функции (printf — НЕ безопасна, использовать write). SIGINT = Ctrl+C. Упомянуть sigaction() как более надёжную альтернативу signal().

Быстрое резюме (~2 мин). Акцент на пунктах 4 и 5 как фундаменте для всего курса. Предсказать, что тема 2 (процессы/потоки) будет опираться на системные вызовы fork/exec и CreateThread.

Рекомендовать студентам ответить дома. Вопросы 3 и 5 — вероятные темы экзаменационных вопросов.

Задание 3 — самое важное, от него зависит понимание материала. Задание 1 — начать на следующем занятии в аудитории. Упомянуть, что задание 4 можно выполнить на примере GetLastError() vs errno/strerror().

Kerrisk (TLPI) — лучшая книга по системному программированию Linux, доступна онлайн. Таненбаум — для теории ОС. man7.org — незаменимый ресурс, научить студентов им пользоваться: man 2 open, man 3 printf.